#include "stdafx.h"
#include "CppUnitTest.h"

#include <vex/iterable/stl_interop.h>
#include <vex/iterable/any_range.h>
#include <vex/iterable/make_any_range.h>
#include <vex.tests/mstest/tests.range/tests.range.h>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace tests {	namespace range_any_output {

    using namespace boost::assign;

    namespace vi = vex::iterable;

    TEST_CLASS(range_any_output)
    {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.range.any.output")
        END_TEST_CLASS_ATTRIBUTE();

        typedef std::vector<int> source_container_t;
        typedef source_container_t::iterator source_iterator_t;
        typedef source_container_t::const_iterator const_source_iterator_t;

        typedef std::list<int> target_container_t;
        typedef target_container_t::iterator target_iterator_t;
        typedef target_container_t::const_iterator const_target_iterator_t;
        typedef std::front_insert_iterator<target_container_t> target_front_inserter_t;
        typedef std::back_insert_iterator<target_container_t> target_back_inserter_t;
        typedef std::insert_iterator<target_container_t> target_inserter_t;

        struct exp_output_range_traits {
            typedef int reference;
            typedef int value_type;
            typedef int* pointer;
        };

        source_container_t 
            m_source, 
            m_source_same_size_different_values, 
            m_source_empty, 
            m_source_with_one_element;

        target_container_t
            m_target;
    public:
        TEST_METHOD_INITIALIZE(set_up)
        {
            source_container_t().swap(m_source);
            source_container_t().swap(m_source_same_size_different_values);
            source_container_t().swap(m_source_empty);
            source_container_t().swap(m_source_with_one_element);
            target_container_t().swap(m_target);

            m_source += 1, 2, 3, 4, 5;
            m_source_same_size_different_values += 6, 7, 8, 9, 5;
            m_source_with_one_element += 1;
        }

        TEST_METHOD_CLEANUP(tear_down)
        {
            source_container_t().swap(m_source);
            source_container_t().swap(m_source_same_size_different_values);
            source_container_t().swap(m_source_empty);
            source_container_t().swap(m_source_with_one_element);
            target_container_t().swap(m_target);
        }

        TEST_METHOD(concepts_and_traits_are_satisfied_if_target_is_back_inserter)
        {
            typedef decltype(vi::make_any_output_range<int>(
                std::back_inserter(m_target))
            ) range_t;
            typedef vi::range_traits<range_t> traits_t;
            typedef std::iterator_traits<target_back_inserter_t> itr_traits_t;

            BOOST_CONCEPT_ASSERT((vi::concept::OutputRange<range_t, source_container_t::value_type>));
            Assert::IsTrue(std::is_same<exp_output_range_traits::reference, traits_t::reference>::value);
            Assert::IsTrue(std::is_same<exp_output_range_traits::value_type, traits_t::value_type>::value);
            Assert::IsTrue(std::is_same<exp_output_range_traits::pointer, traits_t::pointer>::value);
        }

        TEST_METHOD(concepts_and_traits_are_satisfied_if_target_is_front_inserter)
        {
            typedef decltype(vi::make_any_output_range<int>(
                std::front_inserter(m_target))) range_t;
            typedef vi::range_traits<range_t> traits_t;
            typedef std::iterator_traits<target_front_inserter_t> itr_traits_t;

            BOOST_CONCEPT_ASSERT((vi::concept::OutputRange<range_t, source_container_t::value_type>));
            Assert::IsTrue(std::is_same<exp_output_range_traits::reference, traits_t::reference>::value);
            Assert::IsTrue(std::is_same<exp_output_range_traits::value_type, traits_t::value_type>::value);
            Assert::IsTrue(std::is_same<exp_output_range_traits::pointer, traits_t::pointer>::value);
        }

        TEST_METHOD(concepts_and_traits_are_satisfied_if_target_is_inserter)
        {
            typedef decltype(vi::make_any_output_range<int>(
                std::inserter(m_target, m_target.begin()))) range_t;
            typedef vi::range_traits<range_t> traits_t;
            typedef std::iterator_traits<target_inserter_t> itr_traits_t;

            BOOST_CONCEPT_ASSERT((vi::concept::OutputRange<range_t, source_container_t::value_type>));
            Assert::IsTrue(std::is_same<exp_output_range_traits::reference, traits_t::reference>::value);
            Assert::IsTrue(std::is_same<exp_output_range_traits::value_type, traits_t::value_type>::value);
            Assert::IsTrue(std::is_same<exp_output_range_traits::pointer, traits_t::pointer>::value);
        }

        TEST_METHOD(fills_target_container_via_back_inserter)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();

            source_container_t s;
            s += 1, 2, 3, 4;

            target_container_t t;
            auto o = vi::make_any_output_range<int>(std::back_inserter(t));

            Assert::IsTrue(output_range::copy(vi::make_range(s), o));
            one_pass_range::are_equivalent(s.begin(), s.end(), vi::make_range(t));
        }

        TEST_METHOD(fills_target_container_via_front_inserter)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();

            source_container_t s;
            s += 1, 2, 3, 4;

            target_container_t t;
            auto o = vi::make_any_output_range<int>(std::front_inserter(t));

            Assert::IsTrue(output_range::copy(vi::make_range(s), o));
            one_pass_range::are_equivalent(s.rbegin(), s.rend(), vi::make_range(t));
        }

        TEST_METHOD(fills_target_container_via_inserter)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();

            source_container_t s;
            s += 1, 2, 3, 4;

            target_container_t t;
            t += 5, 6, 7;
            auto i = t.begin();
            std::advance(i, 1);
            auto o = vi::make_any_output_range<int>(std::inserter(t, i));
            Assert::IsTrue(output_range::copy(vi::make_range(s), o));

            source_container_t t_exp;
            t_exp += 5, 1, 2, 3, 4, 6, 7;
            one_pass_range::are_equivalent(t_exp.begin(), t_exp.end(), vi::make_range(t));
        }

        TEST_METHOD(fills_target_stream_via_ostream_iterator)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();

            source_container_t s;
            s += 1, 2, 3, 4;

            std::stringstream o;
            Assert::IsTrue(output_range::copy(
                vi::make_range(s), 
                vi::make_any_output_range<int>(std::ostream_iterator<int>(o, " "))));

            std::string t_exp("1 2 3 4 "), t_res = o.str();
            one_pass_range::are_equivalent(vi::make_range(t_exp), vi::make_range(t_res));
        }

        TEST_METHOD(supports_move)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();

            target_container_t t;
            auto t_r1 = vi::make_any_output_range<int>(std::back_inserter(t));
            auto t_r2 = std::move(t_r1);
            
            Assert::IsFalse(t_r1);
            Assert::IsNull(t_r1.get());
            Assert::IsTrue(t_r2);
            Assert::IsNotNull(t_r2.get());
        }

        TEST_METHOD(supports_move_and_swap_for_back_inserter)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();

            source_container_t s1, s2;
            s1 += 1, 2, 3, 4, 5;
            s2 += 6, 7, 8, 9, 10;

            target_container_t t1, t2, t1_exp, t2_exp;
            auto o1 = vi::make_any_output_range<int>(std::back_inserter(t1));
            auto o2 = vi::make_any_output_range<int>(std::back_inserter(t2));

            Assert::IsTrue(output_range::copy_n(vi::make_range(s1), o1, 2));
            Assert::IsTrue(output_range::copy_n(vi::make_range(s2), o2, 2));

            o1.swap(o2);

            Assert::IsTrue(output_range::copy(vi::make_range(s1.begin() + 2, s1.end()), o1));
            Assert::IsTrue(output_range::copy(vi::make_range(s2.begin() + 2, s2.end()), o2));

            t1_exp += 1, 2, 8, 9, 10;
            t2_exp += 6, 7, 3, 4, 5;
            one_pass_range::are_equivalent(vi::make_range(t1_exp), vi::make_range(t1));
            one_pass_range::are_equivalent(vi::make_range(t2_exp), vi::make_range(t2));
        }

        TEST_METHOD(supports_move_and_swap_for_front_inserter)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();

            source_container_t s1, s2;
            s1 += 1, 2, 3, 4, 5;
            s2 += 6, 7, 8, 9, 10;

            target_container_t t1, t2, t1_exp, t2_exp;
            auto o1 = vi::make_any_output_range<int>(std::front_inserter(t1));
            auto o2 = vi::make_any_output_range<int>(std::front_inserter(t2));

            Assert::IsTrue(output_range::copy_n(vi::make_range(s1), o1, 2));
            Assert::IsTrue(output_range::copy_n(vi::make_range(s2), o2, 2));

            o1.swap(o2);

            Assert::IsTrue(output_range::copy(vi::make_range(s1.begin() + 2, s1.end()), o1));
            Assert::IsTrue(output_range::copy(vi::make_range(s2.begin() + 2, s2.end()), o2));

            t1_exp += 10, 9, 8, 2, 1;
            t2_exp += 5, 4, 3, 7, 6;
            one_pass_range::are_equivalent(vi::make_range(t1_exp), vi::make_range(t1));
            one_pass_range::are_equivalent(vi::make_range(t2_exp), vi::make_range(t2));
        }
    };
}}